package ClassData;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;

import ClassData.Visitor.IVisitor;
import ClassData.Visitor.IVisitorElement;

/*
import DataFile.Structure_CP_MethodInfo;
*/

/**
 * @author Tavin
 * @version 1.0
 */

public class ClassData implements IVisitorElement {
	
	public static short ACC_PUBLIC   	= 0x0001;  
	public static short ACC_FINAL  		= 0x0010;  
	public static short ACC_SUPER  		= 0x0020;  
	public static short ACC_INTERFACE  	= 0x0200;  
	public static short ACC_ABSTRACT    = 0x0400;  	
	
	private static final String[] AccesFlagsNames = {"public","final","super","interface","abstract"};
	
	private static final int String_PublicFlagID	= 0;
	private static final int String_FinalFlagID		= String_PublicFlagID+1;
	private static final int String_SuperFlagID		= String_FinalFlagID+1;
	private static final int String_InterfaceFlagID	= String_SuperFlagID+1;
	private static final int String_AbstractFlagID	= String_InterfaceFlagID+1;
	
	
	private short minor_version;
	private short major_version;
 

//	private ConstantPoolInfo constantPool[]; 

	private ConstantPool constantPool;
	
	private short access_flags;
	private short this_class;
	private short super_class;


	private InterfaceInfo interfaces[];

	// Interfaces
	private FieldInfo fields[];
	private MethodInfo methods[];
	private AttributeInfo attributes[];

	ByteBuffer dataBuffer;

	int instanceDataSize;
	int staticDataSize;

	int inheritedInstanceDataSize;
	int inheritedStaticDataSize;
	
	/**
	 *  
	 * 
	 * @param classFile File to be parsed as classfile
	 * 
	*/	
	 public ClassData(File classFile) {
		 inheritedInstanceDataSize=-1;
		 inheritedStaticDataSize=-1;
		 loadClass(classFile);
		 parseClassFile();
	 }
 
/**
 * 
 * @param newConstantPool
 */
public ClassData(ConstantPool newConstantPool) {
	minor_version= (short)0;
	major_version= (short)1;
 

//	private ConstantPoolInfo constantPool[]; 

//	private ConstantPool ;
	
	constantPool = newConstantPool;
	access_flags = ACC_PUBLIC;
	this_class   = 1;
	super_class  = 3;

	interfaces = new InterfaceInfo[0];

	fields     = new FieldInfo[0];
	methods    = new MethodInfo[0];
	attributes = new AttributeInfo [0];
	
	instanceDataSize = 0;
	staticDataSize   = 0;

	inheritedInstanceDataSize = 0;
	inheritedStaticDataSize   = 0;	

}

	/**
	  *  
	  * 
	  * @param classToLoad File to be parsed as class file
	  * 
	 */	
	public void loadClass(File classToLoad) {
		int bufferSize;
	 
		bufferSize=(int) classToLoad.length();
		byte [] tmpDataBuffer;
			
		tmpDataBuffer=new byte[bufferSize];
			
		try {
			InputStream in = new FileInputStream(classToLoad);
			tmpDataBuffer = new byte[(int) classToLoad.length()];
			in.read(tmpDataBuffer);
			in.close();
		} catch (Exception e) {
			// empty
		}	
		dataBuffer =  ByteBuffer.allocate(bufferSize); // TODO Correct BufferSize !!
		dataBuffer.put(tmpDataBuffer);
	}

	 /**
	  *  
	  * 
	  * @param 
	  * 
	 */	
	public void parseClassFile() {
		
		dataBuffer.rewind();
		if (dataBuffer.getInt()!=0xCAFEBABE) {
			System.out.println("Wrong header");
		}
	
		minor_version= dataBuffer.getShort();	
		major_version= dataBuffer.getShort();
	
		parseConstantPool();
		
		// GetConstant Pool
		
		
		access_flags = dataBuffer.getShort();
		this_class   = dataBuffer.getShort();
		super_class  = dataBuffer.getShort();
		
		parseInterfaces();
	
		parseFields();
	
		parseMethods();
	
		parseInfoAttributes();
		
		// TODO Output  Class xyz loaded	
		
	}
		

/****************************************************************************/
/*																			*/
/*	GetUtf8StringFromBuffer								Author OG			*/
/*																			*/
/*	Description																*/
/*																			*/
/*	Version	1.0				Date 2010.12.5				Ststus : working	*/
/*																			*/
/****************************************************************************/

private byte[] getUtf8StringFromBuffer()
{
	byte[] myUtf8String;
	short stringSize;
	int counter;
	
	stringSize=dataBuffer.getShort();
	myUtf8String = new byte[stringSize];
	
	for (counter=0;counter<myUtf8String.length;counter++)
	{
		myUtf8String[counter]=dataBuffer.get();
	}
		
	return(myUtf8String);
}

/**
 *  
 * 
 * @param classFile File to be parsed als class file
 * 
*/	

private void parseConstantPool() {
	short constantPoolSize;
	int counter;
	//short 
	byte tmpByte;
	
	constantPoolSize=dataBuffer.getShort();
	constantPool=new ConstantPool(constantPoolSize-1);   
	
	for (counter=1;counter <constantPoolSize;counter++) {
		tmpByte=dataBuffer.get();
		
		switch (tmpByte) {
		case ConstantPoolInfo.CONSTANT_Class:
			ConstantPoolClass_Info myClassInfo;
			myClassInfo=new ConstantPoolClass_Info(dataBuffer.getShort());
			constantPool.set(counter,myClassInfo);
			break;
			
			
		case ConstantPoolInfo.CONSTANT_Utf8:
			ConstantPoolUtf8_Info myUTF8Info;
			myUTF8Info=new ConstantPoolUtf8_Info(getUtf8StringFromBuffer());
			constantPool.set(counter,myUTF8Info);
			break;
						
			
		case ConstantPoolInfo.CONSTANT_Fieldref:
			ConstantPoolField_Info myFieldInfo;
			
			myFieldInfo= new ConstantPoolField_Info(dataBuffer.getShort(),dataBuffer.getShort());
			constantPool.set(counter,myFieldInfo);
			break;
			
			
		case ConstantPoolInfo.CONSTANT_Methodref:
			ConstantPoolMethod_Info myMethodInfo;
			
			myMethodInfo = new ConstantPoolMethod_Info(dataBuffer.getShort(),dataBuffer.getShort());
			constantPool.set(counter,myMethodInfo);
			break;
			
			
		case ConstantPoolInfo.CONSTANT_InterfaceMethodref:
			break;
			
			
		case ConstantPoolInfo.CONSTANT_String:
			ConstantPoolString_Info myStringInfo;
			
			myStringInfo=new ConstantPoolString_Info(dataBuffer.getShort());
			constantPool.set(counter,myStringInfo);
			break;
			
			
		case ConstantPoolInfo.CONSTANT_Integer:
			ConstantPoolInteger_Info myIntegerInfo;
			
			myIntegerInfo = new ConstantPoolInteger_Info(dataBuffer.getInt());
			constantPool.set(counter,myIntegerInfo);
			break;	
			
			
		case ConstantPoolInfo.CONSTANT_Float:
			ConstantPoolFloat_Info myFloatInfo;
			
			myFloatInfo = new ConstantPoolFloat_Info(dataBuffer.getFloat());
			constantPool.set(counter,myFloatInfo);	
			break;	
			
			
		case ConstantPoolInfo.CONSTANT_Long:
			ConstantPoolLong_Info myLongInfo;
			
			myLongInfo = new ConstantPoolLong_Info(dataBuffer.getLong());
			constantPool.set(counter++,myLongInfo); 
			
			ConstantPoolLongValueDummy_Info myDummyLongInfo;
			myDummyLongInfo=new ConstantPoolLongValueDummy_Info();
			constantPool.set(counter,myDummyLongInfo); // Dummy because of idiotic java Specification !!!  
			break;
			
			
		case ConstantPoolInfo.CONSTANT_Double:
			ConstantPoolDouble_Info myDoubleInfo;
			
			myDoubleInfo = new ConstantPoolDouble_Info(dataBuffer.getDouble());
			constantPool.set(counter++,myDoubleInfo);	
			
			ConstantPoolLongValueDummy_Info myDummyDoubleInfo;
			myDummyDoubleInfo=new ConstantPoolLongValueDummy_Info();
			constantPool.set(counter,myDummyDoubleInfo); // Dummy because of idiotic java Specification !!!  
			break;
			
			
		case ConstantPoolInfo.CONSTANT_NameAndType:
			ConstantPoolName_Info myNameInfo;
			
			myNameInfo= new ConstantPoolName_Info(dataBuffer.getShort(),dataBuffer.getShort());
			constantPool.set(counter,myNameInfo);
			break;
		}
	}
}


	/**
	 *  
	 * 
	 * @param classFile File to be parsed als classfile
	 * 
	 */	
	private void parseInterfaces() {
		int interfacesCount;	
		int interfacessCounter;
	
		InterfaceInfo tmpInterface; 
	
		interfacesCount=dataBuffer.getShort();
	
		interfaces = new InterfaceInfo[interfacesCount];
	
		//TODO Interfaces
	
		for (interfacessCounter=0;interfacessCounter<interfacesCount;interfacessCounter++) {
			tmpInterface=new InterfaceInfo(dataBuffer.getShort());
			interfaces[interfacessCounter]=tmpInterface;
		}
	}

	/**
	 *   
	 * @param classFile File to be parsed 
	 * 
 	 */	
	private void parseFields() {
		int fieldsCount;
		int fieldsCounter;
	
		FieldInfo tmpField;
	
		fieldsCount=dataBuffer.getShort();	
		fields = new FieldInfo[fieldsCount];
		 
		for (fieldsCounter=0;fieldsCounter<fieldsCount;fieldsCounter++) {
			tmpField=new FieldInfo(dataBuffer.getShort(),dataBuffer.getShort(),dataBuffer.getShort());
			fields[fieldsCounter]=tmpField;
			
			constantPool.get(tmpField.getNameIndex());
			ConstantPoolUtf8_Info myUTF8Info;
			
			myUTF8Info = (ConstantPoolUtf8_Info) constantPool.get(tmpField.getNameIndex());
			tmpField.setFieldName(myUTF8Info);
			
			tmpField.setAttributeList(parseInfoAttributes());
		}
	}
	
	/*
	private void findName(FieldInfo myField) {
		
	}
	*/

	/**
	 *  
	 * 
	 * @param 
	 * 
	*/	
	private void parseMethods() {
		MethodInfo tmpMethod;
		int methodsCounter;
		
		methods = new MethodInfo[dataBuffer.getShort()];
		 
		for (methodsCounter=0;methodsCounter<methods.length;methodsCounter++) {
			tmpMethod=new MethodInfo(dataBuffer.getShort(),dataBuffer.getShort(),dataBuffer.getShort());
			methods[methodsCounter]=tmpMethod;
			
			constantPool.get(tmpMethod.getNameIndex());
			ConstantPoolUtf8_Info myUTF8Info;
			
			myUTF8Info = (ConstantPoolUtf8_Info) constantPool.get(tmpMethod.getNameIndex());
			tmpMethod.setMethodName(myUTF8Info);
			tmpMethod.setAttributeList(parseInfoAttributes());
			tmpMethod.parseParameterTypes(constantPool);
		}
	}

	/****************************************************************************/
	/*																			*/
	/*	ParseInfoAttributes									Author OG			*/
	/*																			*/
	/*	Description																*/
	/*																			*/
	/*	Version	1.0				Date 2010.12.6				Status : working	*/
	/*																			*/
	/****************************************************************************/
	private AttributeInfo[] parseInfoAttributes() {
		int InfoAttributeCount;	
		int iCounter;
		AttributeInfo AtributesList[];
		//byte[] AttributeData;
		//int AttributeDataLenght;
		//short AttributeIndex;
		AttributeInfo tmpAttribute;

		InfoAttributeCount=dataBuffer.getShort();		

		AtributesList=new AttributeInfo[InfoAttributeCount];
		for (iCounter=0;iCounter < InfoAttributeCount;iCounter++) {
	
			tmpAttribute = AttributeInfo.parseAttribute(dataBuffer,constantPool);
	
		//	AttributeIndex=dataBuffer.getShort();
			
		//  AttributeData = new byte [AttributeDataLenght];
		//	dataBuffer.get(AttributeData, 0, AttributeData.length);
		//	dataBuffer.
		//  ConstantPool
		//	ConstantPoolUtf8_Info myAttributeName;
		//	myAttributeName=(ConstantPoolUtf8_Info) constantPool.get(AttributeIndex);
		//	tmpAttribute = AttributeInfo.parseAttribute(myAttributeName.GetString(),dataBuffer);
		//  tmpAttribute=new AttributeInfo(AttributeIndex,AttributeData);
			AtributesList[iCounter]=tmpAttribute;
		}
		//attribute_info {
    	//u2 attribute_name_index;
    	//u4 attribute_length;
    	//u1 info[attribute_length];
        //}

		return(AtributesList);
	}

	/****************************************************************************/
	/*																			*/
	/*	GetUtf8FromConstantPool								Author OG			*/
	/*																			*/
	/*	Description																*/
	/*																			*/
	/*	Version	1.0				Date 2010.12.10				Status : working	*/
	/*																			*/
	/****************************************************************************/
	private String getUtf8FromConstantPool(int index) {
		ConstantPoolUtf8_Info myUTF8Info;
	
		if (constantPool.get(index).getClass().getName().equals(ConstantPoolUtf8_Info.class.getName())) {
			myUTF8Info=(ConstantPoolUtf8_Info)constantPool.get(index);
		} else {
			//TODO Exception !!!
			return(null);
		}
		
		return(myUTF8Info.getString());	
	}

	/****************************************************************************/
	/*																			*/
	/*	GetClassName										Author OG			*/
	/*																			*/
	/*	Description																*/
	/*																			*/
	/*	Version	1.0				Date 2010.12.10				Status : working	*/
	/*																			*/
	/****************************************************************************/
	public String getClassName() {
		ConstantPoolClass_Info myClassInfo;
	
		myClassInfo=(ConstantPoolClass_Info)constantPool.get(this_class);	
		return(getUtf8FromConstantPool(myClassInfo.GetNameIndex()));	
	}


	/****************************************************************************/
	/*																			*/
	/*	GetSuperClassName									Author OG			*/
	/*																			*/
	/*	Description																*/
	/*																			*/
	/*	Version	1.0				Date 2010.12.10				Status : working	*/
	/*																			*/
	/****************************************************************************/
	public String getSuperClassName() {
		ConstantPoolClass_Info myClassInfo;
	
		if (super_class > 0) {
			myClassInfo=(ConstantPoolClass_Info)constantPool.get(super_class);
		} else {
			return("");
		}
	
		return(getUtf8FromConstantPool(myClassInfo.GetNameIndex()));
	}

	/**
	 * 
	 * @return
	 */
	public ConstantPool getConstantPool() {
		return(constantPool);
	}

/*
 * 	access_flags=DataBuffer.getShort();
	this_class=DataBuffer.getShort();
	super_class=DataBuffer.getShort();
 */

	/**
	 * 
	 * returns the name of stored class
	 * 
	 *@return String Class name  
	 *  
	*/	
	public FieldInfo[] getFields() {
		return(fields);
	}

	/**
	 * 
	 * @return
	 */
	public MethodInfo[] getMethods() {
		return(methods);
	}

	public String toString() {
		return(getClassName());
	}

/**
 * 
 * returns the name of stored class
 * 
 *@return String Class name  
 *  
*/	

	/**
	 * 
	 * returns the major version of stored class
	 * 
	 *@return int major version  
	 *  
 	 */
	public int getMajorVersion() {
		return(major_version);	
	}

	/**
	 * 
	 * returns the minor version of stored class
	 * 
	 *@return int minor version  
	 *  
	 */
	public int getMinorVersion() {
		return(minor_version);	
	}

	/**
	 * 
	 * returns the fields count of stored class
	 * 
	 *@return int  fields count  
	 *  
	 */
	public int getInterfacesCount() {
		return(interfaces.length);
	}

	/**
	 * 
	 * returns the fields count of stored class
	 * 
	 *@return int  fields count  
	 *  
     */
	public int getFieldsCount() {
		return(fields.length);
	}

	/**
	 * 
	 * @param index
	 * @return
	 */
	public FieldInfo getField(int index) {
		// TODO Range check !
		return(fields[index]);
	}

	/**
	 * 
	 * returns the method count of stored class
	 * 
	 *@return int  method count  
	 *  
	 */	
	public int getMethodsCount() {
		return(methods.length);
	}

	/**
	 * 
	 * returns the fields count of stored class
	 * 
	 *@return int  fields count  
	 *  
	 */
	public int getAttributeCount() {
		return( (attributes == null) ? 0 : attributes.length);
	}

	/**
	 * 
	 * @return
	 */
	public String getAccesFlagsAsString() {
		String flagsAsString="";
		
		if ((access_flags & ACC_PUBLIC) > 0) {
			flagsAsString+=AccesFlagsNames[String_PublicFlagID];
		}
		
		if ((access_flags & ACC_FINAL) > 0) {
			if (flagsAsString.length() > 0) 
				flagsAsString+=", ";
			
			flagsAsString+=AccesFlagsNames[String_FinalFlagID];
		}
		
		if ((access_flags & ACC_SUPER) > 0) {
			if (flagsAsString.length() > 0)
				flagsAsString+=", ";
			
			flagsAsString+=AccesFlagsNames[String_SuperFlagID];
		}
	
		if ((access_flags & ACC_INTERFACE) > 0) {
			if (flagsAsString.length() > 0)
				flagsAsString+=", ";
		
			flagsAsString+=AccesFlagsNames[String_InterfaceFlagID];
		}
	
		if ((access_flags & ACC_ABSTRACT) >0) {
			if (flagsAsString.length()>0)
				flagsAsString+=", ";
	
			flagsAsString+=AccesFlagsNames[String_AbstractFlagID];
		}
		
		return(flagsAsString);	
	}

	/**
	 * 
	 * @return
	 */
	public int getInstanceDataSize() {
		return(instanceDataSize);	
	}

	/**
	 * 
	 * @return
	 */
	public int getStaticDataSize() {
		return(staticDataSize);
	}

	/**
	 * 
	 * @return
	 */
	public boolean isIheritedDataSizeCalculated() {
		if ((inheritedInstanceDataSize ==-1) || (inheritedStaticDataSize==-1))
			return (false);
		
		return (true);	
	}

	/**
	 * 
	 * @param staticData
	 * @param instanceData
	 */
	public void setInheritedDataSize(int staticData, int instanceData) {
		inheritedInstanceDataSize=instanceData;	
		inheritedStaticDataSize=staticData;
	}

	/**
	 * 
	 * @return
	 */
	public int getIinheritedInstanceDataSize() {
		return(inheritedInstanceDataSize);	
	}

	/**
	 * 
	 * @return
	 */
	public int getInheritedStaticDataSize() {
		return(inheritedStaticDataSize);
	}

	/**
	 * 
	 * @return
	 */
	public int getTotalInstanceDataSize() {
		//TODO exception if inherited not set !
		return(instanceDataSize+inheritedInstanceDataSize);	
	}

	/**
	 * 
	 * @return
	 */
	public int getTotalStaticDataSize() {
		//TODO if inheritet not set !
		return(staticDataSize+inheritedStaticDataSize);
	}

	/**
	 * 
	 */
	public void parseClassData() {
		parseFieldDataSize();
		parseMethodsParameters();
	}

	/**
	 * 
	 */
	public void parseFieldDataSize() {
		int fieldCounter;
		
		instanceDataSize=0;
		staticDataSize=0;
		
		for (fieldCounter=0;fieldCounter<fields.length;fieldCounter++)
			{
			fields[fieldCounter].parseFieldType(constantPool);
			if  (fields[fieldCounter].isStatic()) {
				staticDataSize+=fields[fieldCounter].getDataSize();
			} else {
				instanceDataSize+=fields[fieldCounter].getDataSize();
			}	
		}
		
		/*
		 * 	int dataSize;
		int staticDataSize;
		 */
		
	}

	/**
	 * 
	 */
	public void parseMethodsParameters() {
		int cpCounter;
	
		ConstantPoolMethod_Info myMethodInfo;
		ConstantPoolName_Info myNameInfo;
		ConstantPoolUtf8_Info myUtf8Info;
		
		for (cpCounter=1;cpCounter<=constantPool.size();cpCounter++) { 
			if (constantPool.get(cpCounter).getClass().getName().equals(ConstantPoolMethod_Info.class.getName())) {
				myMethodInfo=(ConstantPoolMethod_Info)constantPool.get(cpCounter);
				myNameInfo=(ConstantPoolName_Info)constantPool.get(myMethodInfo.getDescriptorIndex());
			
				myUtf8Info=(ConstantPoolUtf8_Info)constantPool.get(myNameInfo.getDescriptorIndex());
				myMethodInfo.setParametersAsString(myUtf8Info.getString());
			}	
		}
	}

	@Override
	public void receive_Visitor(IVisitor visitor) {
		visitor.visit_ClassData(this);
	}
	
}

 

